#include <assert.h>
#include <iostream>

#include "graph_support/GraphSupport.h"
#include "graph_support/Graph.h"

#ifndef FLOWEDGEATTRIBUTES_H_
#define FLOWEDGEATTRIBUTES_H_

namespace GRAPH_SUPPORT_NS
{
namespace attributes
{

/**
 * describes Capacity interface
 */
class FlowEdgeAttributes : public Graph::EdgeAttributes
{
public:
	class Capacity
	{
	// 4:		cap = 0;
	public:
		Capacity( int );
		const Capacity & operator = ( const int & );

	// compare
	public:
		bool operator < ( const Capacity & ) const;
		bool operator <= ( const Capacity & ) const;
		bool operator > ( const Capacity & ) const;
		bool operator >= ( const Capacity & ) const;
		bool operator == ( const Capacity & ) const;

	// arithmetic
	public:
		const Capacity & operator + ( const Capacity & ) const;
		const Capacity & operator - ( const Capacity & ) const;
		const Capacity & operator += ( const Capacity & ) const;
		const Capacity & operator -= ( const Capacity & ) const;

	protected:
		Capacity() {} // default constructor is available
	};
public:
	// TODO:
//	Capacity getCapacity();
//	void setCapacity( const Capacity & capacity );
//	Capacity getFlow();
//	Capacity getRemainCapacity();
//	void addFlow( Capacity addFlow );
//	void eraseFlow();
// INF
public:
	static Capacity getINFCapacity();
};

// TODO: check BidirectedRequired template argument
template< typename CapT = int, bool BidirectedRequired = true >
class FlowEdgeAttributesTmpl : public FlowEdgeAttributes
{
public:
	typedef CapT Capacity;

private:
	typedef FlowEdgeAttributesTmpl< Capacity > MyT;

public:
	static Capacity getINFCapacity()
	{
		return 1 << 30;
	}

public:
	FlowEdgeAttributesTmpl( const Capacity & capacity = 0 )
		: capacity( capacity )
		, flow( 0 )
		, revertedEdgeAttributes( NULL )
	{
	}

	Capacity getCapacity()
	{
		assert( ! BidirectedRequired || revertedEdgeAttributes != NULL );
		return capacity;
	}

	void setCapacity( const Capacity & capacity )
	{
		assert( flow < capacity + EPS );
		assert( ! BidirectedRequired || revertedEdgeAttributes != NULL );
		this->capacity = capacity;
	}

	Capacity getFlow()
	{
		assert( ! BidirectedRequired || revertedEdgeAttributes != NULL );
		return flow;
	}

	Capacity getRemainCapacity()
	{
		assert( ! BidirectedRequired || revertedEdgeAttributes != NULL );
		return capacity - flow;
	}

	MyT & getRevertedEdgeAttributes()
	{
		assert( ! BidirectedRequired || revertedEdgeAttributes != NULL );
		return * revertedEdgeAttributes;
	}

	void addFlow( Capacity addFlow )
	{
		assert( ! BidirectedRequired || revertedEdgeAttributes != NULL );
		assert( flow + addFlow <= capacity + EPS );
		assert( revertedEdgeAttributes->flow - addFlow <= revertedEdgeAttributes->capacity );
		flow += addFlow;
		revertedEdgeAttributes->flow -= addFlow;
	}

	void eraseFlow()
	{
		assert( ! BidirectedRequired || revertedEdgeAttributes != NULL );
		assert( revertedEdgeAttributes != NULL );
		addFlow( ( Capacity )0 - flow );
		assert( flow == 0 );
		assert( revertedEdgeAttributes->flow == 0 );
	}

	void onBidirEdgeCreated( MyT & revertedAttributes, bool isOriginalEdge = true )
	{
		revertedEdgeAttributes = & revertedAttributes;
		if ( ! isOriginalEdge )
		{
			capacity = 0;
		}
	}

public:
	FlowEdgeAttributesTmpl( const MyT & that )
		: capacity( that.capacity )
		, flow( that.flow )
		, revertedEdgeAttributes( NULL )
	{
	}

	void operator = ( const MyT & that )
	{
		capacity = that.capacity;
		flow = that.flow;
		revertedEdgeAttributes = NULL;
	}

	bool operator == ( const MyT & that ) const
	{
		return this->capacity == that.capacity && this->flow == that.flow;
	}

private:
	double static const EPS = 1e-5;
protected:
	Capacity capacity;
	Capacity flow;
	MyT * revertedEdgeAttributes;
	static const bool staticBidirectedRequired = BidirectedRequired;
};

}
}

#endif /* FLOWEDGEATTRIBUTES_H_ */
